#include "yoshix.h"

#define _USE_MATH_DEFINES

#include <math.h>

using namespace gfx;



namespace
{
    struct SEntity
    {
        float   m_Position[3];
        float   m_AngleX;
        float   m_AngleY;
        float   m_AngleZ;
        BHandle m_pMesh;
        BHandle m_pObject;
    };
} // namespace


namespace
{
class CApplication : public IApplication
    {
        public:

            CApplication();
            virtual ~CApplication();

        private:

            static const int s_NumberOfEntities = 5;
            static const int s_PointsPerHalfCircle = 22;

			float m_SpeedX;
			float m_SpeedZ;

			float m_Radius;

            static const int s_VerticesCount = s_PointsPerHalfCircle * s_PointsPerHalfCircle *2 + s_PointsPerHalfCircle *2 ;
            static const int s_TriangleCount = s_PointsPerHalfCircle * 4 * (s_PointsPerHalfCircle);

            //static const int s_VerticesColorCount = s_TriangleCount * 3 ;
            static const int s_VerticesColorCount = ((s_PointsPerHalfCircle * (s_PointsPerHalfCircle * 2 -2))) * 6 + (s_PointsPerHalfCircle *2 *2) *3 ;

        private:
            float   m_FieldOfViewY;

            SEntity m_Entities[s_NumberOfEntities];

        private:

            virtual bool InternOnStartup();
            virtual bool InternOnShutdown();
            virtual bool InternOnCreateTextures();
            virtual bool InternOnReleaseTextures();
            virtual bool InternOnCreateMeshes();
            virtual bool InternOnReleaseMeshes();
            virtual bool InternOnCreateObjects();
            virtual bool InternOnReleaseObjects();
            virtual bool InternOnResize(int _Width, int _Height);
            virtual bool InternOnKeyEvent(unsigned int _Key, bool _IsKeyDown, bool _IsAltDown);
            virtual bool InternOnMouseEvent(int _X, int _Y, int _Button, bool _IsButtonDown, bool _IsDoubleClick, int _WheelDelta);
            virtual bool InternOnUpdate();
            virtual bool InternOnFrame();
    };
} // namespace

namespace
{
    CApplication::CApplication()
        : m_FieldOfViewY(60.0f),
		  m_SpeedX(0.008f),
		  m_SpeedZ(0.002f),
		  m_Radius(2.0f) {
    }

    // -----------------------------------------------------------------------------

    CApplication::~CApplication()
    {
    }

    // -----------------------------------------------------------------------------

    bool CApplication::InternOnStartup()
    {
      static const float s_Positions[][3] = 
        {
            /*{ -3.0f,   0.0f,  -1.0f },*/
            { 3.0f,   2.0f,  5.0f },
			{ 0.0f,   0.0f,  0.0f },
			{ 0.0f,   0.0f,  0.0f },
			{ 0.0f,   0.0f,  0.0f },
			{ 0.0f, 0.0f,   0.0f }          
        };

        static const float s_ClearColor[4] = 
        { 
            0.0f, 0.0f, 0.0f, 1.0f,
        };

        float LightPosition[3];

        float LightAmbientColor [4];
        float LightDiffuseColor [4];
        float LightSpecularColor[4];

        SetClearColor(s_ClearColor);

        LightPosition[0] = 0.0f;
        LightPosition[1] = 0.0f;
        LightPosition[2] = 4.0f;

        SetLightPosition(LightPosition);

        LightAmbientColor [0] = 0.1f; LightAmbientColor [1] = 0.1f; LightAmbientColor [2] = 0.1f; LightAmbientColor [3] = 1.0f;
        LightDiffuseColor [0] = 0.6f; LightDiffuseColor [1] = 0.6f; LightDiffuseColor [2] = 0.6f; LightDiffuseColor [3] = 1.0f;
        LightSpecularColor[0] = 0.9f; LightSpecularColor[1] = 0.9f; LightSpecularColor[2] = 0.9f; LightSpecularColor[3] = 1.0f;

        SetLightColor(LightAmbientColor, LightDiffuseColor, LightSpecularColor, 127);
		/*SetWireFrame(true);*/

        for (int IndexOfEntitiy = 0; IndexOfEntitiy < s_NumberOfEntities; ++IndexOfEntitiy)
        {
            SEntity& rEntity = m_Entities[IndexOfEntitiy];

            rEntity.m_Position[0] = s_Positions[IndexOfEntitiy][0];
            rEntity.m_Position[1] = s_Positions[IndexOfEntitiy][1];
            rEntity.m_Position[2] = s_Positions[IndexOfEntitiy][2];
            rEntity.m_AngleX      = 0.0f;
            rEntity.m_AngleY      = 0.0f;
            rEntity.m_AngleZ      = 0.0f;
            rEntity.m_pMesh       = 0;
            rEntity.m_pObject     = 0;
        }

        return true;
    }

    // -----------------------------------------------------------------------------

    bool CApplication::InternOnShutdown()
    {
        return true;
    }

    // -----------------------------------------------------------------------------

    bool CApplication::InternOnCreateTextures()
    {

        return true;
    }

    // -----------------------------------------------------------------------------

    bool CApplication::InternOnReleaseTextures()
    {
        return true;
    }

    // -----------------------------------------------------------------------------

    float getRad(float _Grad)
    {
        return((float)(_Grad * M_PI / 180.0));
    }

    // -----------------------------------------------------------------------------

    bool CApplication::InternOnCreateMeshes()
    {
        float SphereVertices[s_VerticesCount][3];        
        float AngleStart = getRad(90);
        float AngleHalf  = getRad(180.0f) + AngleStart;
        float AngleFull  = getRad(360.0f) + AngleStart;
        float AngleStep  = getRad(180.0f / s_PointsPerHalfCircle);
        float ActAngle   = AngleStart;
        int ActIndex = 0;

        for(int IndexHalfCircle = 0; IndexHalfCircle <= s_PointsPerHalfCircle; ++ IndexHalfCircle)
        {
            
           float x = m_Radius * cos(ActAngle);
           float y = m_Radius * sin(ActAngle);
           float ActAngleFull = AngleStart;
           ActAngle += AngleStep;
           
           for(int IndexFullCircle = 0; IndexFullCircle < s_PointsPerHalfCircle * 2; ++ IndexFullCircle)
           {
                SphereVertices[ActIndex][0] = x * cos(ActAngleFull);
                SphereVertices[ActIndex][1] = y;
                SphereVertices[ActIndex][2] = x * sin(ActAngleFull);
                ++ ActIndex;
                ActAngleFull += AngleStep;
           }
        }

        int SphereIndices[s_TriangleCount][3];

        for(int Index = 0; Index < s_TriangleCount / 2; ++Index)
        {
            SphereIndices[Index][0] = Index;
            SphereIndices[Index][1] = Index + s_PointsPerHalfCircle * 2;            
            SphereIndices[Index][2] = Index + s_PointsPerHalfCircle * 2 + 1;

            SphereIndices[Index + s_TriangleCount/2][0] = Index;
            SphereIndices[Index + s_TriangleCount/2][1] = Index + s_PointsPerHalfCircle * 2 + 1;
            SphereIndices[Index + s_TriangleCount/2][2] = Index + 1;


            if((Index + s_PointsPerHalfCircle * 2 + 1) % (s_PointsPerHalfCircle * 2) == 0)
            {
                 SphereIndices[Index][2] -= s_PointsPerHalfCircle * 2;
                 SphereIndices[Index + s_TriangleCount/2][1] -= s_PointsPerHalfCircle * 2;
                 SphereIndices[Index + s_TriangleCount/2][2] -= s_PointsPerHalfCircle * 2;
            }
        }

        float SphereColors[s_VerticesCount][4];

        for(int IndexColor = 0; IndexColor < s_VerticesCount; ++IndexColor)
        {
            if(IndexColor % 18 < 3)
            {
                SphereColors[IndexColor][0] = 0; SphereColors[IndexColor][1] = 0; SphereColors[IndexColor][2] = 1; SphereColors[IndexColor][3] = 1;
            }
            else if(IndexColor % 18 >= 3 && IndexColor % 18 < 6)
            {
                SphereColors[IndexColor][0] = 0; SphereColors[IndexColor][1] = 1; SphereColors[IndexColor][2] = 0; SphereColors[IndexColor][3] = 1;
            }
            else if(IndexColor % 18 >= 6 && IndexColor % 18 < 9)
            {
                SphereColors[IndexColor][0] = 1; SphereColors[IndexColor][1] = 0; SphereColors[IndexColor][2] = 0; SphereColors[IndexColor][3] = 1;
            }
            else if(IndexColor % 18 >= 9 && IndexColor % 18 < 12)
            {
                SphereColors[IndexColor][0] = 0; SphereColors[IndexColor][1] = 1; SphereColors[IndexColor][2] = 1; SphereColors[IndexColor][3] = 1;
            }
            else if(IndexColor % 18 >= 12 && IndexColor % 18 < 15)
            {
                SphereColors[IndexColor][0] = 1; SphereColors[IndexColor][1] = 0; SphereColors[IndexColor][2] = 1; SphereColors[IndexColor][3] = 1;
            }
            else
            {
                SphereColors[IndexColor][0] = 1; SphereColors[IndexColor][1] = 1; SphereColors[IndexColor][2] = 0; SphereColors[IndexColor][3] = 1;
            }   
        }

        // -----------------------------------------------------------------------------
        
        int TriangleIndex = 0;
        float SphereVerticesColor[s_VerticesColorCount][3];
        for(int Index = 0; Index < s_VerticesColorCount;  Index += 3)
        {
            
            SphereVerticesColor[Index][0] = SphereVertices[SphereIndices[TriangleIndex][0]][0];
            SphereVerticesColor[Index][1] = SphereVertices[SphereIndices[TriangleIndex][0]][1];
            SphereVerticesColor[Index][2] = SphereVertices[SphereIndices[TriangleIndex][0]][2];

            SphereVerticesColor[Index+1][0] = SphereVertices[SphereIndices[TriangleIndex][1]][0];
            SphereVerticesColor[Index+1][1] = SphereVertices[SphereIndices[TriangleIndex][1]][1];
            SphereVerticesColor[Index+1][2] = SphereVertices[SphereIndices[TriangleIndex][1]][2];

            SphereVerticesColor[Index+2][0] = SphereVertices[SphereIndices[TriangleIndex][2]][0];
            SphereVerticesColor[Index+2][1] = SphereVertices[SphereIndices[TriangleIndex][2]][1];
            SphereVerticesColor[Index+2][2] = SphereVertices[SphereIndices[TriangleIndex][2]][2];
            ++ TriangleIndex;
        }

        float SphereTriangleColors[s_VerticesColorCount][4];

        for(int IndexTriangleColor = 0; IndexTriangleColor < s_VerticesColorCount; ++ IndexTriangleColor)
        {
            if(IndexTriangleColor % 18 < 3)
            {
                SphereTriangleColors[IndexTriangleColor][0] = 0; SphereTriangleColors[IndexTriangleColor][1] = 0; SphereTriangleColors[IndexTriangleColor][2] = 1; SphereTriangleColors[IndexTriangleColor][3] = 1;
            }
            else if(IndexTriangleColor % 18 >= 3 && IndexTriangleColor % 18 < 6)
            {
                SphereTriangleColors[IndexTriangleColor][0] = 0; SphereTriangleColors[IndexTriangleColor][1] = 1; SphereTriangleColors[IndexTriangleColor][2] = 0; SphereTriangleColors[IndexTriangleColor][3] = 1;
            }
            else if(IndexTriangleColor % 18 >= 6 && IndexTriangleColor % 18 < 9)
            {
                SphereTriangleColors[IndexTriangleColor][0] = 1; SphereTriangleColors[IndexTriangleColor][1] = 0; SphereTriangleColors[IndexTriangleColor][2] = 0; SphereTriangleColors[IndexTriangleColor][3] = 1;
            }
            else if(IndexTriangleColor % 18 >= 9 && IndexTriangleColor % 18 < 12)
            {
                SphereTriangleColors[IndexTriangleColor][0] = 0; SphereTriangleColors[IndexTriangleColor][1] = 1; SphereTriangleColors[IndexTriangleColor][2] = 1; SphereTriangleColors[IndexTriangleColor][3] = 1;
            }
            else if(IndexTriangleColor % 18 >= 12 && IndexTriangleColor % 18 < 15)
            {
                SphereTriangleColors[IndexTriangleColor][0] = 1; SphereTriangleColors[IndexTriangleColor][1] = 0.0f; SphereTriangleColors[IndexTriangleColor][2] = 0.0f; SphereTriangleColors[IndexTriangleColor][3] = 1;
            }
            else
            {
                SphereTriangleColors[IndexTriangleColor][0] = 0; SphereTriangleColors[IndexTriangleColor][1] = 0; SphereTriangleColors[IndexTriangleColor][2] = 0; SphereTriangleColors[IndexTriangleColor][3] = 1;
            }
        }

        int SphereIndicesColor[s_TriangleCount][3];
        int PointsIndex = 0;

        for(int TirangleIndex = 0; TirangleIndex < s_TriangleCount;  ++ TirangleIndex)
        {
           
            SphereIndicesColor[TirangleIndex][0] = PointsIndex;
            SphereIndicesColor[TirangleIndex][1] = PointsIndex +1;
            SphereIndicesColor[TirangleIndex][2] = PointsIndex +2;
            PointsIndex +=3;
        }

        /*CreateColoredMesh(&SphereVertices[0][0], &SphereColors[0][0], s_VerticesCount, &SphereIndices[0][0], s_TriangleCount * 3, &m_Entities[0].m_pMesh); */       

        CreateColoredMesh(&SphereVerticesColor[0][0], &SphereTriangleColors[0][0], s_VerticesColorCount, &SphereIndicesColor[0][0], s_TriangleCount * 3, &m_Entities[0].m_pMesh);



		static const float s_Wall1Vertices[][3] =
        {
            {  0.0f,  0.0f,  0.0f, },
            {  0.0f,  0.0f, 20.0f, },
            {  0.0f,  4.0f, 20.0f, },
            {  0.0f,  4.0f,  0.0f, },
        };

		static const int s_Wall1Indices[][3] =
        {
            {  0,  1,  3, },
            {  3,  1,  2, },
        };
		CreateMesh(&s_Wall1Vertices[0][0], 4, &s_Wall1Indices[0][0], 6, &m_Entities[1].m_pMesh);

		static const float s_Wall2Vertices[][3] =
        {
            {  0.0f,  0.0f, 20.0f, },
            { 20.0f,  0.0f, 20.0f, },
            { 20.0f,  4.0f, 20.0f, },
            {  0.0f,  4.0f, 20.0f, },
        };

		static const int s_Wall2Indices[][3] =
        {
            {  0,  1,  3, },
            {  3,  1,  2, },
        };
		CreateMesh(&s_Wall2Vertices[0][0], 4, &s_Wall2Indices[0][0], 6, &m_Entities[2].m_pMesh);

		static const float s_Wall3Vertices[][3] =
        {
            { 20.0f,  0.0f,  0.0f, },
            { 20.0f,  0.0f, 20.0f, },
            { 20.0f,  4.0f, 20.0f, },
            { 20.0f,  4.0f,  0.0f, },
        };

		static const int s_Wall3Indices[][3] =
        {
            {  0,  1,  3, },
            {  3,  1,  2, },
        };
		CreateMesh(&s_Wall3Vertices[0][0], 4, &s_Wall3Indices[0][0], 6, &m_Entities[3].m_pMesh);

		static const float s_Wall4Vertices[][3] =
        {
            {  0.0f,  0.0f,  0.0f, },
            { 20.0f,  0.0f,  0.0f, },
            { 20.0f,  4.0f,  0.0f, },
            {  0.0f,  4.0f,  0.0f, },
        };

		static const int s_Wall4Indices[][3] =
        {
            {  0,  1,  3, },
            {  3,  1,  2, },
        };
		CreateMesh(&s_Wall4Vertices[0][0], 4, &s_Wall2Indices[0][0], 6, &m_Entities[4].m_pMesh);
       
        return true;
    }

     bool CApplication::InternOnReleaseMeshes()
    {
        for (int IndexOfEntitiy = 0; IndexOfEntitiy < s_NumberOfEntities; ++ IndexOfEntitiy)
        {
            SEntity& rEntity = m_Entities[IndexOfEntitiy];

            ReleaseMesh(rEntity.m_pMesh);
        }

        return true;
    }

    // -----------------------------------------------------------------------------

    bool CApplication::InternOnCreateObjects()
    {
        for (int IndexOfEntitiy = 0; IndexOfEntitiy < s_NumberOfEntities; ++ IndexOfEntitiy)
        {
            SEntity& rEntity = m_Entities[IndexOfEntitiy];

            CreateObject(rEntity.m_pMesh, &rEntity.m_pObject);
        }

        return true;
    }

    // -----------------------------------------------------------------------------

    bool CApplication::InternOnReleaseObjects()
    {
        for (int IndexOfEntitiy = 0; IndexOfEntitiy < s_NumberOfEntities; ++ IndexOfEntitiy)
        {
            SEntity& rEntity = m_Entities[IndexOfEntitiy];

            ReleaseObject(rEntity.m_pObject);
        }

        return true;
    }

    // -----------------------------------------------------------------------------

    bool CApplication::InternOnResize(int _Width, int _Height)
    {
        float ProjectionMatrix[16];

        GetProjectionMatrix(m_FieldOfViewY, static_cast<float>(_Width) / static_cast<float>(_Height), 0.1f, 100.0f, ProjectionMatrix);

        SetProjectionMatrix(ProjectionMatrix);
		

        return true;
    }

    // -----------------------------------------------------------------------------

    bool CApplication::InternOnKeyEvent(unsigned int _Key, bool _IsKeyDown, bool _IsAltDown)
    {
        return true;
    }

    // -----------------------------------------------------------------------------

    bool CApplication::InternOnMouseEvent(int _X, int _Y, int _Button, bool _IsButtonDown, bool _IsDoubleClick, int _WheelDelta)
    {
        return true;
    }

    // -----------------------------------------------------------------------------

    bool CApplication::InternOnUpdate()
    {
        float Eye[3];
        float At [3];
        float Up [3];

        float TranslationMatrix[16];
        float RotationXMatrix  [16];
        float RotationYMatrix  [16];
        float RotationZMatrix  [16];
        float WorldMatrix      [16];
        float ViewMatrix       [16];

        Eye[0] = 30.0f; At[0] = 2.0f; Up[0] = 0.0f;
        Eye[1] = 20.0f; At[1] = 0.0f; Up[1] = 1.0f;
        Eye[2] = -15.0f; At[2] = 4.0f; Up[2] = 0.0f;

        GetViewMatrix(Eye, At, Up, ViewMatrix);
        SetViewMatrix(ViewMatrix);

        for (int IndexOfEntitiy = 0; IndexOfEntitiy < s_NumberOfEntities; ++ IndexOfEntitiy)
        {
            SEntity& rEntity = m_Entities[IndexOfEntitiy];

			// --------------------------------------------------------------------------------
			// Calculate sphere-movement
			// --------------------------------------------------------------------------------
			if (IndexOfEntitiy == 0)
			{
				rEntity.m_Position[0] += m_SpeedX;
				rEntity.m_Position[2] += m_SpeedZ;

				if (rEntity.m_Position[0] < m_Radius)
				{
					rEntity.m_Position[0] = m_Radius;
					m_SpeedX = -m_SpeedX;
				}
				else if (rEntity.m_Position[0] > (20.0f - m_Radius))
				{
					rEntity.m_Position[0] = (20.0f - m_Radius);
					m_SpeedX = -m_SpeedX;
				}

				if (rEntity.m_Position[2] < m_Radius)
				{
					rEntity.m_Position[2] = m_Radius;
					m_SpeedZ = -m_SpeedZ;
				}
				else if (rEntity.m_Position[2] > (20.0f - m_Radius))
				{
					rEntity.m_Position[2] = (20.0f - m_Radius);
					m_SpeedZ = -m_SpeedZ;
				}

				rEntity.m_AngleY = ::fmodf(rEntity.m_AngleY + 0.1f, 360.0f);
			}

            GetTranslationMatrix(rEntity.m_Position[0], rEntity.m_Position[1], rEntity.m_Position[2], TranslationMatrix);

            GetRotationYMatrix(rEntity.m_AngleY, RotationYMatrix);

			MulMatrix(RotationYMatrix, TranslationMatrix, WorldMatrix);

            SetWorldMatrix(rEntity.m_pObject, WorldMatrix);
        }
        return true;
    }

    bool CApplication::InternOnFrame()
    {
        for (int IndexOfEntitiy = 0; IndexOfEntitiy < s_NumberOfEntities; ++ IndexOfEntitiy)
        {
            SEntity& rEntity = m_Entities[IndexOfEntitiy];

            DrawObject(rEntity.m_pObject);
        }

        return true;
    }
}//namespace

void main()
{
    CApplication Application;

    OpenWindow(800, 600, "BALL-Box");

    StartupDirectX();

    RunApplication(&Application);

    ShutdownDirectX();

    CloseWindow();
}